﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace G.Animation.PathAnimation
{
    public enum PathAnimationSource
    {
        // Summary:
        //     Specifies the x-coordinate offset during the progression along an animation
        //     sequence path.
        X = 0,
        //
        // Summary:
        //     Specifies the y-coordinate offset during the progression along an animation
        //     sequence path.
        Y = 1,
        // Added by Won Dong(fkiller@gmail.com)
        // Summery:
        //     Specifies the angle during progression along an animation sequence path.
        Angle = 2
    }

    public class DoubleAnimationUsingPath : BaseAnimationUsingPath
    {
        #region Source DP
        // Summary:
        //     Specifies the output property value of the path that is used to drive the
        //     animation.  Default value is X.
        public PathAnimationSource Source
        {
            get { return (PathAnimationSource)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register("Source", typeof(PathAnimationSource), typeof(DoubleAnimationUsingPath),
            new PropertyMetadata((PathAnimationSource)PathAnimationSource.X,
                 new PropertyChangedCallback(OnSourcePropertyChanged)));

        static void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DoubleAnimationUsingPath anim = d as DoubleAnimationUsingPath;

            if (anim == null)
                return;

            if (e.NewValue == e.OldValue)
                return;

            // Source property has changed, so indicate that the animation is 
            // dirty (i.e., we will need to rebuild the storyboard the next time 'begin' 
            // is called.  This means, that if an animation is currently in progress, 
            // then changing any of these values will not take effect until the next 
            // time 'begin' is called. This avoid unncessary overhead of rebuilding 
            // storyboard everytime 'begin' is called.
            anim.IsStoryDirty = true;
        }

        #endregion

        public override void CreateStory()
        {
            if (!IsStoryDirty)
                return;

            if (Duration == null ||
                Target == null ||
                TargetProperty == null ||
                PathGeometry == null ||
                PathGeometry is GeometryGroup) // won't work with GeometryGroups!
            {
                Story = null;
                return;
            }

            Story = new Storyboard();
            Story.BeginTime = BeginTime;
            Story.Duration = Duration;
            Story.AutoReverse = AutoReverse;
            Story.FillBehavior = FillBehavior;
            Story.RepeatBehavior = RepeatBehavior;

            Story.Completed += delegate(object sender, EventArgs e) { OnCompleted(e); };

            Storyboard.SetTarget(Story, Target);
            Storyboard.SetTargetProperty(Story, TargetProperty);

            PathGeometryHelper helper = new PathGeometryHelper();
            PathGeometry flattenedGeometry = helper.FlattenGeometry(PathGeometry, Tolerance);

            double length;

            List<Point> points = GetPointsOnFlattenedPath(flattenedGeometry, out length);

            DoubleAnimationUsingKeyFrames keyFrames = new DoubleAnimationUsingKeyFrames();

            // handle first keyframe 
            LinearDoubleKeyFrame keyFrameZero = new LinearDoubleKeyFrame();
            keyFrameZero.KeyTime = TimeSpan.Zero;

            if (Source == PathAnimationSource.X)
                keyFrameZero.Value = points[0].X;
            else if (Source == PathAnimationSource.Y)
                keyFrameZero.Value = points[0].Y;
            else if (Source == PathAnimationSource.Angle)
                keyFrameZero.Value = (Convert.ToDouble(points[1].X < points[0].X) * 180.0) + Math.Atan((points[1].Y - points[0].Y) / (points[1].X - points[0].X)) * 180.0 / Math.PI;

            keyFrames.KeyFrames.Add(keyFrameZero);

            double accumLength = 0;

            for (int i = 0; i < points.Count - 1; i++)
            {
                accumLength += CalculateDistance(points[i], points[i + 1]);
                double fractionOfOverAllLength = accumLength / length;

                LinearDoubleKeyFrame keyFrame = new LinearDoubleKeyFrame();

                keyFrame.KeyTime = new TimeSpan(0, 0, 0, 0, (int)(Duration.TotalMilliseconds * fractionOfOverAllLength));

                if (Source == PathAnimationSource.X)
                    keyFrame.Value = points[i + 1].X;
                else if (Source == PathAnimationSource.Y)
                    keyFrame.Value = points[i + 1].Y;
                else if (Source == PathAnimationSource.Angle)
                    keyFrame.Value = (Convert.ToDouble(points[i + 1].X < points[i].X) * 180.0) + Math.Atan((points[i + 1].Y - points[i].Y) / (points[i + 1].X - points[i].X)) * 180.0 / Math.PI;

                keyFrames.KeyFrames.Add(keyFrame);
            }

            Story.Children.Add(keyFrames);

            IsStoryDirty = false;
        }
    }
}
